/*
 * Window.java  0.4.0 / Oct 27, 2010
 *
 * Copyright (c) 2010 - 2011, cute4j.com and contributors.
 *
 * This software is licensed under the LGPL version 2.1 license.
 * A copy of this license is included in the file LICENSE-LGPL.txt.
 */
package com.cute4j.swing;

import com.cute4j.swing.events.SwingEvent;
import com.cute4j.swing.events.WindowEvent;
import com.cute4j.swing.events.WindowListener;
import com.cute4j.swing.graphics.Dimension;
import com.cute4j.swing.graphics.Image;
import com.cute4j.swing.graphics.ImageProducer;
import com.cute4j.swing.layouts.BorderLayout;
import com.cute4j.swing.layouts.LayoutManager;
import com.cute4j.swing.properties.WindowProperties;

/**
 * Window Component.
 *
 * @cute4j.native
 *    generate="header"
 *
 * @author Slobodan Vrkacevic
 * @version 0.4.0
 */
public class Window extends Container
                    implements WindowConstants, WindowProperties {

// Public Static Fields. -------------------------------------------------------

  /**
   * <tt>Window</tt> window type (the value is {@value}).
   */
  public static final int TYPE_WINDOW = 1;

  /**
   * <tt>Dialog</tt> window type (the value is {@value}).
   */
  public static final int TYPE_DIALOG = 3;

  /**
   * <tt>Tool</tt> window type (the value is {@value}).
   */
  public static final int TYPE_POPUP = 9;

  /**
   * <tt>Tool</tt> window type (the value is {@value}).
   */
  public static final int TYPE_TOOL = 11;

  /**
   * <tt>Splash screen</tt> window type (the value is {@value}).
   */
  public static final int TYPE_SPLASH_SCREEN = 15;

  /**
   * <tt>Undecorated</tt> border decoration (the value is {@value}).
   */
  public static final int BORDER_UNDECORATED = 0x00000800;

  /**
   * <tt>Undecorated</tt> border decoration (the value is {@value}).
   */
  public static final int BORDER_TITLE = 0x00001000;

  /**
   * <tt>Undecorated</tt> border decoration (the value is {@value}).
   */
  public static final int BORDER_SYSTEM_MENU = 0x00002000;

  /**
   * <tt>Undecorated</tt> border decoration (the value is {@value}).
   */
  public static final int BORDER_MINIMIZE_BUTTON = 0x00004000;

  /**
   * <tt>Undecorated</tt> border decoration (the value is {@value}).
   */
  public static final int BORDER_MAXIMIZE_BUTTON = 0x00008000;

  /**
   * <tt>Undecorated</tt> border decoration (the value is {@value}).
   */
  public static final int BORDER_HELP_BUTTON = 0x00010000;

  /**
   * <tt>True</tt> if window opacity is supported on the platform.
   */
  public static final boolean WINDOW_OPACITY_SUPPORTED;

  /**
   * <tt>True</tt> if always-on-top windows are supported on the platform.
   */
  public static final boolean ALWAYS_ON_TOP_WINDOW_SUPPORTED;

  /**
   * <tt>True</tt> if full screen window is supported on the platform.
   */
  public static final boolean FULL_SCREEN_WINDOW_SUPPORTED;

  /**
   * <tt>True</tt> if custom window shape is supported on the platform.
   */
  public static final boolean WINDOW_SHAPE_SUPPORTED;

// Package-Private Static Fields. ----------------------------------------------

  static final int BORDER_DECORATION_CHECK = ~(BORDER_UNDECORATED |
                                               BORDER_TITLE |
                                               BORDER_SYSTEM_MENU |
                                               BORDER_MINIMIZE_BUTTON |
                                               BORDER_MAXIMIZE_BUTTON |
                                               BORDER_HELP_BUTTON);

  static final Window[] EMPTY_WINDOW_ARRAY = new Window[0];

// Static Initialization. ------------------------------------------------------

  static {
    _initialize();
    WINDOW_OPACITY_SUPPORTED = _opacitySupported();
    ALWAYS_ON_TOP_WINDOW_SUPPORTED = _alwaysOnTopSupported();
    FULL_SCREEN_WINDOW_SUPPORTED = _fullScreenSupported();
    WINDOW_SHAPE_SUPPORTED = _windowShapeSupported();
  }

// Package-Private Fields. -----------------------------------------------------

  int windowType;
  String title;
  Image iconImage;
  int defaultCloseOperation;
  double opacity;
  int state;
  int modalityType;
  int borderDecoration;
  boolean alwaysOnTop;
  boolean undecorated;
  Window[] ownedWindows;
  int ownedWindowCount;

// Public Constructors. --------------------------------------------------------

  /**
   * Constructs new Window object.
   */
  public Window() {
    this(new BorderLayout(), null);
  }

  /**
   * Constructs new Window object.
   *
   * @param title the window title
   */
  public Window(String title) {
    this(new BorderLayout(), title);
  }

  /**
   * Constructs new Window object.
   *
   * @param layoutManager the layout manager
   */
  public Window(LayoutManager layoutManager) {
    this(layoutManager, null);
  }

  /**
   * Constructs new Window object.
   *
   * @param layoutManager the layout manager
   * @param title the window title
   */
  public Window(LayoutManager layoutManager, String title) {
    super(layoutManager);
    windowType = TYPE_DIALOG;
    defaultCloseOperation = HIDE_ON_CLOSE;
    this.title = title;
    _setTitle(_delegate, title); // NOPMD
    ownedWindows = new Window[1];
  }

// Public Static Methods. ------------------------------------------------------

  /**
   * Returns all live (non disposed) windows.
   * <p>
   *   <tt>NOTE: A window in the returned array can be disposed after method
   *             call!</tt>
   * </p>
   *
   * @return all live (non disposed) windows
   */
  public static Window[] getWindows() {
    synchronized (Swing.CLASS_LOCK) {
      if (Swing.isDisposed()) {
        return EMPTY_WINDOW_ARRAY; // NOFB NOPMD
      }
      return _getWindows();
    }
  }

  /**
   * Returns active window.
   *
   * @return active window
   */
  public static Window getActiveWindow() {
    synchronized (Swing.CLASS_LOCK) {
      if (Swing.isDisposed()) {
        return null;
      }
      return _getActiveWindow();
    }
  }

// Public Methods. -------------------------------------------------------------

  /**
   * Returns window type.
   *
   * @return window type
   */
  public int getWindowType() {
    return windowType;
  }

  /**
   * Returns window title.
   *
   * @return window title
   */
  public String getTitle() {
    return title;
  }

  /**
   * Sets window title.
   *
   * @param title the window title
   */
  public void setTitle(String title) {
    final String oldTitle = this.title;
    if (oldTitle == null) {
      if (title == null) {
        return;
      }
    } else {
      if (oldTitle.equals(title)) {
        return;
      }
    }
    disposedCheck();
    _setTitle(_delegate, title);
    this.title = title;
    firePropertyChange(PROPERTY_TITLE, oldTitle, title);
  }

  /**
   * Returns icon image.
   *
   * @return icon image
   */
  public Image getIconImage() {
    return iconImage;
  }

  /**
   * Sets the icon image.
   *
   * @param iconImage the icon image
   */
  public void setIconImage(Image iconImage) {
    final Image oldIconImage = this.iconImage;
    if (oldIconImage == null) {
      if (iconImage == null) {
        return;
      }
    } else {
      if (oldIconImage.equals(iconImage)) {
        return;
      }
    }
    disposedCheck();
    if (oldIconImage instanceof SwingImage) {
      ((SwingImage)oldIconImage).releaseImagePointer();
    }
    if (iconImage == null) {
      _setIconImage(_delegate, 0);
    } else {
      if (iconImage instanceof SwingImage) {
        SwingImage image = (SwingImage)iconImage;
        _setIconImage(_delegate, image.acquireImagePointer());
        image.releaseImagePointer();
      } else {
        ImageProducer imageProducer = iconImage.getSource();
        SwingImage.SwingImageConsumer imageConsumer =
                                          new SwingImage.SwingImageConsumer();
        imageProducer.startProduction(imageConsumer);
        _setIconImage(_delegate, imageConsumer.pixels, imageConsumer.length);
      }
    }
    this.iconImage = iconImage;
    firePropertyChange(PROPERTY_ICON_IMAGE, oldIconImage, iconImage);
  }

  /**
   * Returns window opacity.
   *
   * @return window opacity
   */
  public double getOpacity() {
    return opacity;
  }

  /**
   * Sets window opacity.
   *
   * @param opacity the opacity
   */
  public void setOpacity(double opacity) {
    final double oldOpacity = this.opacity;
    if (Math.abs(oldOpacity - opacity) > 0.001) {
      disposedCheck();
      _setOpacity(_delegate, opacity);
      this.opacity = opacity;
      firePropertyChange(PROPERTY_OPACITY, oldOpacity, opacity);
    }
  }

  /**
   * Returns default close operation.
   *
   * @return default close operation
   */
  public int getDefaultCloseOperation() {
    return defaultCloseOperation;
  }

  /**
   * Sets default close operation.
   *
   * @param operation the default operation
   */
  public void setDefaultCloseOperation(int operation) {
    if ((operation < DO_NOTHING_ON_CLOSE) || (operation > QUIT_ON_CLOSE)) {
      throw new IllegalArgumentException("Invalid default close operation");
    }
    final int oldDefaultCloseOperation = this.defaultCloseOperation;
    if (oldDefaultCloseOperation != operation) {
      disposedCheck();
      defaultCloseOperation = operation;
      _setAcceptClose(_delegate, operation != DO_NOTHING_ON_CLOSE);
      firePropertyChange(PROPERTY_DEFAULT_CLOSE_OPERATION,
                         oldDefaultCloseOperation, operation);
    }
  }

  /**
   * Returns window state.
   *
   * @return window state
   */
  public int getState() {
    return state;
  }

  /**
   * Sets window state.
   *
   * @param state the window state
   */
  public void setState(int state) {
    if ((state != NORMAL) && (state != ICONIFIED) && (state != MAXIMIZED) &&
        (state != FULL_SCREEN)) {
      throw new IllegalArgumentException("Invalid window state");
    }
    final int oldState = this.state;
    if (oldState != state) {
      disposedCheck();
      _setState(_delegate, state);
      this.state = state;
    }
  }

  /**
   * Returns <tt>true</tt> if the window is modal.
   *
   * @return <tt>true</tt> if the window is modal
   */
  public boolean isModal() {
    return (modalityType != NON_MODAL);
  }

  /**
   * Returns window modality.
   *
   * @return window modality
   */
  public int getModalityType() {
    return modalityType;
  }

  /**
   * Sets window modality.
   *
   * @param modalityType the modality type
   */
  public void setModalityType(int modalityType) {
    if ((modalityType < NON_MODAL) || (modalityType > APPLICATION_MODAL)) {
      throw new IllegalArgumentException("Invalid modality type");
    }
    final int oldModalityType = this.modalityType;
    if (oldModalityType != modalityType) {
      disposedCheck();
      _setModality(_delegate, modalityType);
      this.modalityType = modalityType;
    }
  }

  /**
   * Returns <tt>true</tt> if the window is an always-on-top window.
   *
   * @return <tt>true</tt> if the window is an always-on-top window
   */
  public boolean isAlwaysOnTop() {
    return alwaysOnTop;
  }

  /**
   * Sets this window to be always above other windows.
   *
   * @param alwaysOnTop if <tt>true</tt> the window is always above other
   *                    windows
   */
  public void setAlwaysOnTop(boolean alwaysOnTop) {
    final boolean oldAlwaysOnTop = this.alwaysOnTop;
    if (oldAlwaysOnTop != alwaysOnTop) {
      disposedCheck();
      _setAlwaysOnTop(_delegate, alwaysOnTop);
      this.alwaysOnTop = alwaysOnTop;
      firePropertyChange(PROPERTY_ALWAYS_ON_TOP, oldAlwaysOnTop, alwaysOnTop);
    }
  }

  /**
   * Returns <tt>true</tt> if the window is full screen.
   *
   * @return <tt>true</tt> if the window is full screen
   */
  public boolean isFullScreen() {
    return (state == FULL_SCREEN);
  }

  /**
   * Sets full screen window.
   *
   * @param fullScreen if <tt>true</tt> the window is full screen window
   */
  public void setFullScreen(boolean fullScreen) {
    if (fullScreen) {
      setState(FULL_SCREEN);
    } else {
      setState(NORMAL);
    }
  }

  /**
   * Toggles full screen.
   */
  public void toggleFullScreen() {
    setFullScreen(state != FULL_SCREEN);
  }

  /**
   * Sets location of the window relative to the specified window.
   *
   * @param window the window
   */
  public void setLocationRelativeTo(Window window) {
    disposedCheck();
    if (window == null) {
      _setLocationRelativeTo(_delegate, 0);
    } else {
      if (window.isDisposed()) {
        throw new IllegalArgumentException("Window disposed");
      }
      _setLocationRelativeTo(_delegate, window._delegate);
    }
  }

  /**
   * Brings window to the front.
   */
  public void toBack() {
    if (visible) {
      disposedCheck();
      _toBack(_delegate);
    }
  }

  /**
   * Sends window to the back.
   */
  public void toFront() {
    if (visible) {
      disposedCheck();
      _toFront(_delegate);
    }
  }

  /**
   * Returns border decoration.
   *
   * @return border decoration
   */
  public int getBorderDecoration() {
    return borderDecoration;
  }

  /**
   * Sets border decoration.
   *
   * @param borderDecoration the border decoration
   */
  public void setBorderDecoration(int borderDecoration) { // NOPMD
    if ((borderDecoration & BORDER_DECORATION_CHECK) != 0) {
      throw new IllegalArgumentException("Invalid border decoration");
    }
    if (fixedSize != null) {
      borderDecoration = borderDecoration & ~BORDER_MAXIMIZE_BUTTON;
      if (borderDecoration == 0) {
        return;
      }
    }
    if (this.borderDecoration != borderDecoration) {
      disposedCheck();
      _setBorderDecoration(_delegate, borderDecoration);
      this.borderDecoration = borderDecoration;
    }
  }

  /**
   * Returns <tt>true</tt> if the window is undecorated (frameless).
   *
   * @return <tt>true</tt> if the window is undecorated
   */
  public boolean isUndecorated() {
    return (borderDecoration & BORDER_UNDECORATED) != 0;
  }

  /**
   * Sets/unsets window decoration.
   *
   * @param undecorated if <tt>false</tt> the window is undecorated.
   */
  public void setUndecorated(boolean undecorated) {
    if (undecorated) {
      setBorderDecoration(BORDER_UNDECORATED);
    } else {
      setBorderDecoration(borderDecoration & ~BORDER_UNDECORATED);
    }
  }

  /**
   * Adds window listener.
   *
   * @param listener the window listener
   */
  public void addWindowListener(WindowListener listener) {
    if (listener != null) {
      enableEvents(SwingEvent.WINDOW_EVENT_MASK);
      listenerList.add(WindowListener.class, listener);
    }
  }

  /**
   * Removes window listener.
   *
   * @param listener the window listener
   */
  public void removeWindowListener(WindowListener listener) {
    if (listener != null) {
      listenerList.remove(WindowListener.class, listener);
      if (listenerList.getListenerCount(WindowListener.class) == 0) {
        disableEvents(SwingEvent.WINDOW_EVENT_MASK);
      }
    }
  }

  /**
   * Returns window listeners.
   *
   * @return window listeners
   */
  public WindowListener[] getWindowListeners() {
    int count = listenerList.getListenerCount(WindowListener.class);
    WindowListener[] listeners = new WindowListener[count];
    if (count > 0) {
      System.arraycopy(listenerList.getListenersArray(WindowListener.class),
                       0, listeners,  0, count);
    }
    return listeners;
  }

  /**
   * Returns owner of this window.
   *
   * @return owner of this window
   */
  public final Window getWindowOwner() {
    return (Window)owner;
  }

  /**
   * Sets the owner of this window.
   *
   * @param owner the owner
   */
  public void setWindowOwner(Window owner) {
    if ((owner != null) && (this.owner != owner)) { // NOPMD
      disposedCheck();
      owner.acquireWindow(this);
    }
  }

  /**
   * Return an array containing all the window this window currently owns.
   *
   * @return an array containing all the window this window currently owns
   */
  public Window[] getOwnedWindows() {
    final Window[] ownedWindows = new Window[ownedWindowCount];
    System.arraycopy(this.ownedWindows, 0, ownedWindows, 0, ownedWindowCount);
    return ownedWindows;
  }

// Protected Methods. ----------------------------------------------------------

  /**
   * Sets window type.
   *
   * @param windowType the window type
   */
  protected void setWindowType(int windowType) {
    if ((windowType != TYPE_WINDOW) && (windowType != TYPE_DIALOG) &&
        (windowType != TYPE_POPUP) && (windowType != TYPE_TOOL) &&
        (windowType != TYPE_SPLASH_SCREEN)) {
      throw new IllegalArgumentException("Invalid window type");
    }
    if (this.windowType != windowType) {
      disposedCheck();
      _setWindowType(_delegate, windowType);
      this.windowType = windowType;
    }
  }

  /**
   * Process window event.
   *
   * @param event the event
   */
  protected void processWindowEvent(WindowEvent event) {
    if (eventEnabled(SwingEvent.WINDOW_EVENT_MASK)) {
      int count = listenerList.getListenerCount(WindowListener.class);
      if (count > 0) {
        WindowListener[] listeners = (WindowListener[])listenerList.
                                        getListenersArray(WindowListener.class);
        switch (event.getID()) { // NOPMD
          case WindowEvent.WINDOW_OPENED:
            for (int i = count; --i >= 0;) {
              listeners[i].windowOpened(event);
            }
            break;
          case WindowEvent.WINDOW_CLOSING:
            for (int i = count; --i >= 0;) {
              listeners[i].windowClosing(event);
            }
            break;
          case WindowEvent.WINDOW_CLOSED:
            for (int i = count; --i >= 0;) {
              listeners[i].windowClosed(event);
            }
            break;
          case WindowEvent.WINDOW_ACTIVATED:
            for (int i = count; --i >= 0;) {
              listeners[i].windowActivated(event);
            }
            break;
          case WindowEvent.WINDOW_DEACTIVATED:
            for (int i = count; --i >= 0;) {
              listeners[i].windowDeactivated(event);
            }
            break;
          case WindowEvent.WINDOW_GAINED_FOCUS:
            for (int i = count; --i >= 0;) {
              listeners[i].windowGainedFocus(event);
            }
            break;
          case WindowEvent.WINDOW_LOST_FOCUS:
            for (int i = count; --i >= 0;) {
              listeners[i].windowLostFocus(event);
            }
            break;
          case WindowEvent.WINDOW_STATE_CHANGED:
            for (int i = count; --i >= 0;) {
              listeners[i].windowStateChanged(event);
            }
            break;
        }
      }
    }
    if (event.getID() == WindowEvent.WINDOW_CLOSING) {
      doDefaultCloseOperation();
    }
  }

// Overridden Methods. ---------------------------------------------------------

  /**
   * {@inheritDoc}
   */
  public void setFixedSize(Dimension fixedSize) {
    super.setFixedSize(fixedSize);
    if (fixedSize != null) {
      borderDecoration = borderDecoration & ~BORDER_MAXIMIZE_BUTTON;
    }
  }

  /**
   * {@inheritDoc}
   */
  public void updateLocalization() {
    super.updateLocalization();
    if (title != null) {
      _setTitle(_delegate, localizeString(title));
    }
  }

  /**
   * {@inheritDoc}
   */
  public Component[] getOwnedComponents() {
    final Component[] components = new Component[componentCount +
                                                 ownedWindowCount];
    System.arraycopy(this.components, 0, components, 0, componentCount);
    System.arraycopy(ownedWindows, 0, components, componentCount,
                     ownedWindowCount);
    return components;
  }

  /**
   * {@inheritDoc}
   */
  protected String paramString() {
    StringBuffer sb = new StringBuffer(super.paramString());
    if (title != null) {
      sb.append(",title=\"");
      sb.append(title);
      sb.append('"');
    }
    return sb.toString();
  }

  void acquireWindow(Window window) {
    if (window == this) {
      throw new IllegalArgumentException("Self owning");
    }
    Window previousOwner = (Window)window.owner;
    if (previousOwner != null) {
      previousOwner.releaseWindow(window);
    }
    disposedCheck();
    _setWindowOwner(window._delegate, _delegate);
    window.owner = this;
    if (ownedWindowCount >= ownedWindows.length) {
      final Window[] oldOwnedWindow = ownedWindows;
      int newCapacity = (3 * ownedWindows.length) >> 1;
      if (++newCapacity < ownedWindowCount) {
        newCapacity = ownedWindowCount;
      }
      ownedWindows = new Window[newCapacity];
      System.arraycopy(oldOwnedWindow, 0, ownedWindows, 0, ownedWindowCount);
    }
    ownedWindows[ownedWindowCount++] = window;
  }

  void releaseWindow(Window window) {
    if ((window != null) && (window.owner == this)) { // NOPMD
      disposedCheck();
      _setWindowOwner(window._delegate, 0);
      window.owner = null;
      final Window[] ownedWindows = this.ownedWindows;
      int index = ownedWindowCount;
      while (--index >= 0) {
        if (ownedWindows[index] == window) { // NOPMD
          break;
        }
      }
      for (int i = index + 1; i < ownedWindowCount; i++) {
        ownedWindows[index++] = ownedWindows[i];
      }
      ownedWindows[--ownedWindowCount] = null;
    }
  }

// Package-Private Methods. ----------------------------------------------------

  void doDefaultCloseOperation() {
    switch(defaultCloseOperation) { // NOPMD
      case DISPOSE_ON_CLOSE:
         dispose();
         break;
      case EXIT_ON_CLOSE:
        System.exit(0); // NOFB
        break;
      case QUIT_ON_CLOSE:
        Swing.stopEventLoop(0);
        break;
    }
  }

// Native Callbacks. -----------------------------------------------------------

  void _windowOpened() {
    processWindowEvent(new WindowEvent(this, WindowEvent.WINDOW_OPENED));
  }

  void _windowClosing() {
    processWindowEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
  }

  void _windowClosed() {
    processWindowEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSED));
  }

  void _windowActivated() {
    processWindowEvent(new WindowEvent(this, WindowEvent.WINDOW_ACTIVATED));
  }

  void _windowDeactivated() {
    processWindowEvent(new WindowEvent(this, WindowEvent.WINDOW_DEACTIVATED));
  }

  void _windowGainedFocus() {
    processWindowEvent(new WindowEvent(this, WindowEvent.WINDOW_GAINED_FOCUS));
  }

  void _windowLostFocus() {
    processWindowEvent(new WindowEvent(this, WindowEvent.WINDOW_LOST_FOCUS));
  }

  void _windowStateChanged(int oldState, int newState) {
    processWindowEvent(new WindowEvent(this, WindowEvent.WINDOW_STATE_CHANGED,
                                       oldState, newState));
  }

// Package-Private Native Methods. ---------------------------------------------

  native long _create();

  native void _setWindowType(long delegate, int windowType);

  native void _setTitle(long delegate, String title);

  native void _setIconImage(long delegate, long imagePointer);

  native void _setIconImage(long delegate, byte[] imageData, int length);

  native void _setOpacity(long delegate, double opacity);

  native void _setAcceptClose(long delegate, boolean accept);

  native void _setModality(long delegate, int modality);

  native void _setState(long delegate, int state);

  native void _setAlwaysOnTop(long delegate, boolean alwaysOnTop);

  native void _setLocationRelativeTo(long delegate, long windowDelegate);

  native void _toFront(long delegate);

  native void _toBack(long delegate);

  native void _setBorderDecoration(long delegate, int borderDecoration);

// Private Static Native Methods. ----------------------------------------------

  private static native void _initialize();

  private static native boolean _opacitySupported();

  private static native boolean _alwaysOnTopSupported();

  private static native boolean _fullScreenSupported();

  private static native boolean _windowShapeSupported();

  private static native Window[] _getWindows();

  private static native Window _getActiveWindow();

  private static native void _setWindowOwner(long windowDelegate,
                                             long ownerDelegate);

}
